/****************************************************************************
 **
 ** This file is part of the yFiles extension package GraphML-3.2-yFiles-2.7.
 ** 
 ** yWorks proprietary/confidential. Use is subject to license terms.
 **
 ** Redistribution of this file or of an unauthorized byte-code version
 ** of this file is strictly forbidden.
 **
 ** Copyright (c) 2000-2009 by yWorks GmbH, Vor dem Kreuzberg 28, 
 ** 72070 Tuebingen, Germany. All rights reserved.
 **
 ***************************************************************************/

package demo.yext.graphml;

import java.awt.Color;
import java.awt.Graphics2D;
import java.awt.Shape;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.geom.AffineTransform;
import java.awt.geom.Ellipse2D;
import java.awt.geom.GeneralPath;
import java.awt.geom.RectangularShape;
import java.awt.geom.RoundRectangle2D;
import java.util.HashMap;
import java.util.Map;
import javax.swing.JComboBox;
import javax.swing.JToolBar;
import javax.swing.JMenu;

import org.graphdrawing.graphml.reader.GraphMLParseContext;
import org.graphdrawing.graphml.writer.GraphMLWriteContext;
import org.graphdrawing.graphml.writer.XmlWriter;
import org.graphdrawing.graphml.GraphMLConstants;
import org.w3c.dom.NamedNodeMap;
import org.w3c.dom.Node;

import y.view.AbstractCustomHotSpotPainter;
import y.view.AbstractCustomNodePainter;
import y.view.GenericNodeRealizer;
import y.view.NodeRealizer;
import y.view.SimpleUserDataHandler;
import yext.graphml.graph2D.GenericNodeRealizerSerializer;

/**
 * This class demonstrates how to create customized node realizers 
 * of type {@link GenericNodeRealizer} and how to read and write 
 * these customized types in GraphML format. 
 */
public class GenericNodeRealizerDemo extends GraphMLDemo
{
  
  /** Create the demo */
  public GenericNodeRealizerDemo()
  {
    super();
    getGraphMLIOHandler().getRealizerSerializerManager().addNodeRealizerSerializer(new MyGenericNodeRealizerSerializer());
    
    // create a default NodeRealizer
    GenericNodeRealizer cnr = new GenericNodeRealizer();
    
    // get the factory to register our own styles
    GenericNodeRealizer.Factory factory = GenericNodeRealizer.getFactory();
    
    
    // use a map to pass in our implementations
    Map implementationsMap = new HashMap();
    // create custom implementations for ...
    
    // the painter and contains test
    CustomPainter painter = new CustomPainter(new Ellipse2D.Double());
    // register the painter
    implementationsMap.put(GenericNodeRealizer.Painter.class, painter);
    // and the contains test
    implementationsMap.put(GenericNodeRealizer.ContainsTest.class, painter);
    
    // create a custom hotspot painter and hot spot hit test
    CustomHotSpotPainter chsp = new CustomHotSpotPainter(165, new Ellipse2D.Double(), null);
    // register the painter
    implementationsMap.put(GenericNodeRealizer.HotSpotPainter.class, chsp);
    // and the hit test
    implementationsMap.put(GenericNodeRealizer.HotSpotHitTest.class, chsp);

    // a simple default implementation that can deal with cloneable and serializable userdata....
    implementationsMap.put(GenericNodeRealizer.UserDataHandler.class, new SimpleUserDataHandler(SimpleUserDataHandler.REFERENCE_ON_FAILURE));
    
    // finally add the configuration to the factory
    factory.addConfiguration("type1", implementationsMap);
    
    // do the same with two different styles...
    painter = new CustomPainter(new RoundRectangle2D.Double(50,50,50,50,15, 15));
    implementationsMap.put(GenericNodeRealizer.Painter.class, painter);
    implementationsMap.put(GenericNodeRealizer.ContainsTest.class, painter);
    chsp = new CustomHotSpotPainter(255, new Ellipse2D.Double(), Color.red);
    implementationsMap.put(GenericNodeRealizer.HotSpotPainter.class, chsp);
    implementationsMap.put(GenericNodeRealizer.HotSpotHitTest.class, chsp);
    factory.addConfiguration("type2", implementationsMap);
    
    GeneralPath gp = new GeneralPath();
    gp.moveTo(1.0f, 0.5f);
    gp.lineTo(0.0f, 1.0f);
    gp.quadTo(0.0f, 0.5f, 0.3f, 0.5f);
    gp.quadTo(0.0f, 0.5f, 0.0f, 0.0f);
    gp.closePath();
    
    PolygonPainter pp = new PolygonPainter(gp);
    implementationsMap.put(GenericNodeRealizer.Painter.class, pp);
    implementationsMap.put(GenericNodeRealizer.ContainsTest.class, pp);
    factory.addConfiguration("type3", implementationsMap);
    
    // initialize the default noderealizer to the type we just registered...
    cnr.setConfiguration("type1");
    cnr.setUserData(new UserData(1));
    // set the realizer...
    view.getGraph2D().setDefaultNodeRealizer(cnr);
  }
  
  /** Create a toolbar to switch the default node realizer type */
  protected JToolBar createToolBar()
  {
    JToolBar retValue;
    
    retValue = super.createToolBar();
    final JComboBox cb = new JComboBox(new Object[]{"type1", "type2", "type3"});
    retValue.add(cb);
    cb.addActionListener(new ActionListener()
    {
      public void actionPerformed(ActionEvent ae)
      {
        GenericNodeRealizer gnr = (GenericNodeRealizer)view.getGraph2D().getDefaultNodeRealizer();
        gnr.setConfiguration(cb.getSelectedItem().toString());
        gnr.setUserData(new UserData(cb.getSelectedIndex()+1));
      }
    });
    return retValue;
  }

  protected JMenu createSampleGraphMenu() {
    return null;
  }


  /**
   * A custom HotSpotPainter implementation
   */
  static final class CustomHotSpotPainter extends AbstractCustomHotSpotPainter
  {
    
    private RectangularShape shape;
    private Color color;
    
    CustomHotSpotPainter(int mask, RectangularShape shape, Color color)
    {
      super(mask);
      this.shape = shape;
      this.color = color;
    }
    
    protected void initGraphics(NodeRealizer context, Graphics2D g)
    {
      super.initGraphics(context, g);
      if (color == null)
      {
        Color fc = context.getFillColor();
        if (fc != null)
        {
          g.setColor(fc);
        }
      }
      else
      {
        g.setColor(color);
      }
    }
    
    
    protected void paint(byte hotSpot, double centerX, double centerY, Graphics2D graphics)
    {
      shape.setFrame(centerX - 2, centerY - 2, 5, 5);
      graphics.fill(shape);
    }
    
    protected boolean isHit(byte hotSpot, double centerX, double centerY, double testX, double testY)
    {
      return Math.abs(testX - centerX) < 3 && Math.abs(testY - centerY) < 3;
    }
    
  }
  
  /**
   * A custom Painter and ContainsTest implementation.
   * This one works with any kind of RectangularShape
   */
  static final class CustomPainter extends AbstractCustomNodePainter implements GenericNodeRealizer.ContainsTest
  {
    RectangularShape shape;
    
    CustomPainter(RectangularShape shape)
    {
      this.shape = shape;
    }
    
    /** Override default fill color */
    protected Color getFillColor(NodeRealizer context, boolean selected)
    {
      if (selected)
      {
        return Color.red;
      }
      else
      {
        return super.getFillColor(context, selected);
      }
    }
    
    protected void paintNode(NodeRealizer context, Graphics2D graphics, boolean sloppy)
    {
      shape.setFrame(context.getX(), context.getY(), context.getWidth(), context.getHeight());
      if (initializeFill(context, graphics))
      {
        graphics.fill(shape);
      }
      if (initializeLine(context, graphics))
      {
        graphics.draw(shape);
      }
      UserData data = (UserData)((GenericNodeRealizer)context).getUserData();
      graphics.setColor(Color.black);
      graphics.drawString("data=" + data.value,
                          (float)context.getX(),
                          (float)(context.getY() - 1));
    }
    
    public boolean contains(NodeRealizer context, double x, double y)
    {
      shape.setFrame(context.getX(), context.getY(), context.getWidth(), context.getHeight());
      return shape.contains(x, y);
    }
  }
  
  /**
   * Another custom Painter and ContainsTest implementation.
   * This one works with any kind of GeneralPath
   */
  static final class PolygonPainter extends AbstractCustomNodePainter implements GenericNodeRealizer.ContainsTest
  {
    GeneralPath path;
    AffineTransform aft;
    PolygonPainter(GeneralPath path)
    {
      this.path = path;
      this.aft = AffineTransform.getScaleInstance(1.0d, 1.0d);
    }
    
    protected void paintNode(NodeRealizer context, Graphics2D graphics, boolean sloppy)
    {
      aft.setToIdentity();
      aft.translate(context.getX(), context.getY());
      aft.scale(context.getWidth(), context.getHeight());
      Shape shape = path.createTransformedShape(aft);
      if (initializeFill(context, graphics))
      {
        graphics.fill(shape);
      }
      if (initializeLine(context, graphics))
      {
        graphics.draw(shape);
      }
      UserData data = (UserData)((GenericNodeRealizer)context).getUserData();
      graphics.setColor(Color.black);
      graphics.drawString("data=" + data.value,
                          (float)context.getX(),
                          (float)(context.getY() + context.getHeight() + 8));
    }
    
    /** Override default fill color to be the same as the unselected fill color */
    protected Color getFillColor(NodeRealizer context, boolean selected)
    {
      return super.getFillColor(context, false);
    }
    
    public boolean contains(NodeRealizer context, double x, double y)
    {
      return path.contains((x - context.getX()) / context.getWidth(), (y - context.getY()) / context.getHeight());
    }
  }
  
  /**
   * The type for the user data that is associated with GenericNodeRealizer.
   */
  static class UserData {
    int value;
    UserData(int value) {
      this.value = value;
    }
  }
  
  /**
   * Serializer for instances of {@link y.view.GenericNodeRealizer}.
   * Demonstrates how the serialization of user data can be customized. 
   */
  static class MyGenericNodeRealizerSerializer extends GenericNodeRealizerSerializer
  {
    /**
     * Writes the userdata section of this realizer and assigns the user data object. 
     */
    protected void writeUserData(GenericNodeRealizer gnr, XmlWriter writer, GraphMLWriteContext context)
    {
      if (gnr.getUserData() instanceof UserData)
      {
        UserData data = (UserData)gnr.getUserData();
        writer.writeStartElement("UserData", GraphMLConstants.YWORKS_EXT_NS_URI)
            .writeAttribute("class", data.getClass().getName())
            .writeAttribute("value", String.valueOf( data.value ) )
            .writeEndElement();
      }
      else {
        super.writeUserData(gnr, writer, context);
      }
    }
    
    /**
     * Parses the userdata section of this realizer and assigns the user data object. 
     */
    protected void parseUserData(GenericNodeRealizer gnr,Node domNode, GraphMLParseContext context) 
    {
      String name = domNode.getLocalName();
      if (!"UserData".equals(name)) {
        return;
      }

      NamedNodeMap nm = domNode.getAttributes();
      Node a;
      a = nm.getNamedItem("class");
      if(UserData.class.getName().equals(a.getNodeValue())) {
        a = nm.getNamedItem("value");
        gnr.setUserData(new UserData(Integer.parseInt(a.getNodeValue())));
      }
      else {
        super.parseUserData(gnr, domNode, context);
      }
    }
  }
  
  
  /**
   * Launcher method. Execute this class to see sample instantiations of
   * the CustomNodeRealizer in action.
   */
  public static void main(String[] args)
  {
    initLnF();
    final GenericNodeRealizerDemo demo = new GenericNodeRealizerDemo();
    demo.start();
  }
}
